using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;

namespace MinecraftGPS.Data
{
    class NamedBinaryTagFile
    {
        public static IEnumerable<Tag> OpenNBTFile(string path)
        {
            if (!File.Exists(path))
                yield break;

            using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
            using (GZipStream gzip = new GZipStream(file, CompressionMode.Decompress))
            using (NBTBinaryReader reader = new NBTBinaryReader(gzip))
            {
                Tag tag = null;
                do
                {
                    tag = ReadTag(reader);
                    if (tag != null)
                        yield return tag;
                } while (tag != null);
            }
        }

        private static byte[] ReadByteArray(NBTBinaryReader reader)
        {
            var length = reader.ReadInt32();
            return reader.ReadBytes(length);
        }

        private static object[] ReadTagArray(NBTBinaryReader reader)
        {
            int tagID = reader.ReadByte();
            int length = reader.ReadInt32();
            object[] tagArr = new object[length];
            for (int i = 0; i < length; i++)
            {
                switch (tagID)
                {
                    case 1:
                        tagArr[i] = reader.ReadByte();
                        break;
                    case 2:
                        tagArr[i] = reader.ReadInt16();
                        break;
                    case 3:
                        tagArr[i] = reader.ReadInt32();
                        break;
                    case 4:
                        tagArr[i] = reader.ReadInt64();
                        break;
                    case 5:
                        tagArr[i] = reader.ReadSingle();
                        break;
                    case 6:
                        tagArr[i] = reader.ReadDouble();
                        break;
                    case 7:
                        tagArr[i] = ReadByteArray(reader);
                        break;
                    case 8:
                        tagArr[i] = reader.ReadString();
                        break;
                    case 9:
                        tagArr[i] = ReadTagArray(reader);
                        break;
                    case 10:
                        tagArr[i] = new Tag("", null, ReadCompoundTags(reader));
                        break;
                    default:
                        throw new NotSupportedException();
                }
            }
            return tagArr;
        }

        private static IEnumerable<Tag> ReadCompoundTags(NBTBinaryReader reader)
        {
            List<Tag> compoundTags = new List<Tag>();
            var tag = ReadTag(reader);
            while (!tag.IsEndTag)
            {
                compoundTags.Add(tag);
                tag = ReadTag(reader);
            }
            return compoundTags.ToArray();
        }

        private static Tag ReadTag(NBTBinaryReader reader)
        {
            int tagType = reader.ReadByte();
            if (tagType == -1)
                return null;
            if (tagType == 0)
                return new Tag();

            string name = reader.ReadString();

            switch (tagType)
            {
                case 1:
                    return new Tag(name, reader.ReadByte());
                case 2:
                    return new Tag(name, reader.ReadInt16());
                case 3:
                    return new Tag(name, reader.ReadInt32());
                case 4:
                    return new Tag(name, reader.ReadInt64());
                case 5:
                    return new Tag(name, reader.ReadSingle());
                case 6:
                    return new Tag(name, reader.ReadDouble());
                case 7:
                    return new Tag(name, ReadByteArray(reader));
                case 8:
                    return new Tag(name, reader.ReadString());
                case 9:
                    return new Tag(name, ReadTagArray(reader));
                case 10:
                    return new Tag(name, null, ReadCompoundTags(reader));
                default:
                    throw new NotSupportedException();
            }
        }
    }
}
